﻿///////////////////////////////////////////////////////////////////////////////
//
//  This source is part of the  "Managed Task Framework.NET Runtime"  (MTF.NET Runtime).
//
//
//  Copyright (c) 2011, Multicore Consulting Limited (UK)
//  All rights reserved.
//  http://www.multicoreconsulting.co.uk
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions
//  are met:
//
//  -  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//  -  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in
//     the documentation and/or other materials provided with the
//     distribution.
//  -  Neither the name of Multicore Consulting Limited nor the names of
//     its contributors may be used to endorse or promote products
//     derived from this software without specific prior written
//     permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
//  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MTFOrg.MTF.MTFCore.Messages;
using System.Threading;

namespace MTFOrg.MTF.MTFCore
{
    /// <summary>
    /// Class implementing the actioner object.
    /// </summary>
    partial class Actioner
    {
        /// <summary>
        /// Defines an interface for handling an action. 
        /// </summary>
        public interface IActionerHandler
        {
            /// <summary>
            /// Handles an action.
            /// </summary>
            /// <param name="actioner">Actioner that triggered this action</param>
            /// <param name="actionEvent">Event that triggered the action</param>
            /// <param name="elementNumber">Actioner element number</param>
            void OnAction(Actioner actioner, EventDictionary actionEvent, int elementNumber);
        }

        /// <summary>
        /// Default actioner handler called when none is provided.
        /// </summary>
        public class Handler : IActionerHandler
        {
            /// <summary>
            /// Actioner owning this default actioner handler.
            /// </summary>
            private Actioner m_Actioner;

            /// <summary>
            /// Gets or sets the owning actioner.
            /// </summary>
            public Actioner OwningActioner
            {
                get { return m_Actioner; }
                set { m_Actioner = value; }
            }

            /// <summary>
            /// Handles an action.
            /// </summary>
            /// <param name="actioner">Actioner that triggered this action</param>
            /// <param name="actionEvent">Event that triggered the action</param>
            /// <param name="elementNumber">Actioner element number</param>
            public virtual void OnAction(Actioner actioner, EventDictionary actionEvent, int elementNumber)
            {
                Console.WriteLine("Actioner '{0}' triggered on element number {1}.", m_Actioner.FullPath, elementNumber);
            }

            /// <summary>
            /// Called to perform thread-safe initialization.
            /// </summary>
            public virtual void OnInitialize()
            {
                // Do nothing by default
            }
        }
        /// <summary>
        /// Handles routing received events to the actioner delegate.
        /// </summary>
        /// <remarks>
        /// This is provided to hide the lower-level EventReceived event.
        /// </remarks>
        protected class EventHandler
        {
            /// <summary>
            /// Reference to actioner that will receive events.
            /// </summary>
            protected IActionerHandler m_ActionerHandler;

            /// <summary>
            /// Reference to producer consumer that will produce events.
            /// </summary>
            protected ProducerConsumer m_ProducerConsumer;

            /// <summary>
            /// Reference to actioner task.
            /// </summary>
            protected Actioner m_Actioner;

            /// <summary>
            /// Execution priority.
            /// </summary>
            protected int m_Priority;

            /// <summary>
            /// Whether to run items in the GUI worker pool.
            /// </summary>
            bool m_IsGui;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="producerConsumer">Reference to producer consumer that will produce events</param>
            /// <param name="actionerHandler">Actioner handler that will handle the event</param>
            public EventHandler(ProducerConsumer producerConsumer, IActionerHandler actionerHandler, Actioner actioner, int priority, bool isGui)
            {
                m_ActionerHandler = actionerHandler;
                m_ProducerConsumer = producerConsumer;
                m_Actioner = actioner;
                m_Priority = priority;
                m_IsGui = isGui;

                // Hook up to the producer-consumer event notifications
                m_ProducerConsumer.EventReceived += new ProducerConsumer.OnEvent(OnEventReceived);
                m_ProducerConsumer.ReleasedReceived += new ProducerConsumer.OnReleased(OnReleasedReceived);
            }

            /// <summary>
            /// Adds the read events under the given root to the given list.
            /// </summary>
            /// <param name="rootEvent">Root event to search from</param>
            /// <param name="readEvents">List of read events</param>
            private void GetReadEvents(Event rootEvent, List<Event> readEvents)
            {
                if (rootEvent is QueueEvent)
                {
                    QueueEvent queueEvent = rootEvent as QueueEvent;
                    if (queueEvent.SourceQueue == QueueType.WrittenItemQueue)
                        readEvents.Add(rootEvent);
                }
                else if (rootEvent is EventArray)
                {
                    EventArray eventArray = rootEvent as EventArray;
                    foreach (Event e in eventArray.Events)
                        GetReadEvents(e, readEvents);
                }
            }

            /// <summary>
            /// Adds the write events under the given root to the given list.
            /// </summary>
            /// <param name="rootEvent">Root event to search from</param>
            /// <param name="writeEvents">List of write events</param>
            private void GetWriteEvents(Event rootEvent, List<Event> writeEvents)
            {
                if (rootEvent is QueueEvent)
                {
                    QueueEvent queueEvent = rootEvent as QueueEvent;
                    if (queueEvent.SourceQueue == QueueType.NewItemQueue)
                        writeEvents.Add(rootEvent);
                }
                else if (rootEvent is EventArray)
                {
                    EventArray eventArray = rootEvent as EventArray;
                    foreach (Event e in eventArray.Events)
                        GetReadEvents(e, writeEvents);
                }
            }

            /// <summary>
            /// Returns the index of the thread with the most number of read events.
            /// </summary>
            /// <param name="readEvents">List of read events</param>
            /// <returns>Best thread index</returns>
            private int GetBestThreadIndex(List<Event> readEvents)
            {
                int maxThreads = Environment.ProcessorCount;
                int[] threadEventCount = new int[maxThreads];
                int maxCount = 0;
                int bestThreadIndex = -1;

                foreach (Event e in readEvents)
                {
                    if (e is QueueEvent)
                    {
                        QueueEvent queueEvent = e as QueueEvent;
                        if (queueEvent.ThreadAffinity != QueueEvent.NO_AFFINITY)
                        {
                            threadEventCount[queueEvent.ThreadAffinity]++;
                            if (threadEventCount[queueEvent.ThreadAffinity] > maxCount)
                            {
                                maxCount = threadEventCount[queueEvent.ThreadAffinity];
                                bestThreadIndex = queueEvent.ThreadAffinity;
                            }
                        }
                    }
                }

                return bestThreadIndex;
            }

            /// <summary>
            /// Sets the thread index for all write events with thread affinity.
            /// </summary>
            /// <param name="writeEvents">List of write events</param>
            /// <param name="threadIndex">Thread index to set in write events</param>
            private void SetWriteEventsThreadIndex(List<Event> writeEvents, int threadIndex)
            {
                foreach (Event e in writeEvents)
                {
                    if (e is QueueEvent)
                    {
                        QueueEvent queueEvent = e as QueueEvent;
                        if (queueEvent.HasAffinity)
                            queueEvent.ThreadAffinity = threadIndex;
                    }
                }
            }

            /// <summary>
            /// Called when the ProducerConsumer receives an event
            /// </summary>
            /// <param name="consumerLinkNum">Consumer link (absolute) on which the event arrived</param>
            /// <param name="consumerSlots">Consumer slot state information</param>
            /// <param name="producerSlots">Producer slot state information</param>
            /// <param name="eventReceived">Event that was received</param>
            /// <param name="elementState">State associated with the current element</param>
            /// <param name="elementNumber">Number of the current element</param>
            public void OnEventReceived(int consumerLinkNum, Slots slots, int consumerSlot, int producerSlot, Event eventReceived, ref object elementState, int elementNumber)
            {
                // Attempt to send a new query
                bool querySent = m_ProducerConsumer.SendQuery(0, Slots.NO_SLOT, elementNumber);

                EventDictionary eventDictionary = eventReceived as EventDictionary;

                // Action the event asynchronously on the preferred thread
                try
                {
                    List<Event> readEvents = new List<Event>();
                    List<Event> writeEvents = new List<Event>();
                    foreach (string key in eventDictionary.Keys)
                    {
                        GetReadEvents(eventDictionary[key], readEvents);
                        GetWriteEvents(eventDictionary[key], writeEvents);
                    }
                    int threadIndex = GetBestThreadIndex(readEvents);

                    // Offload actioner execution to the most appropriate thread
                    m_ProducerConsumer.TaskManager.QueueUserWorkItem(delegate(object state)
                    {
                        SetWriteEventsThreadIndex(writeEvents, MTFRuntime.TaskManager.CurrentThreadIndex);
                        if (m_ActionerHandler != null)
                        {
                            m_ActionerHandler.OnAction(m_Actioner, eventDictionary, elementNumber);
                        }

                        // Dispose of the event and request again
                        m_Actioner.m_ConsumerTask.LockTaskElement(elementNumber);
                        {
                            m_ProducerConsumer.SendRelease(consumerSlot, eventReceived, false, 0, elementNumber, !querySent);
                        }
                        m_Actioner.m_ConsumerTask.UnlockTaskElement(elementNumber);

                    }, null, m_Priority, threadIndex, m_IsGui);
                }
                catch (InvalidOperationException)
                {
                    // Suppress exception thrown on exit
                }
            }

            /// <summary>
            /// Called when the producer notifies it has finished with an event.
            /// </summary>
            /// <param name="consumerLinkNum">Link that query was received on</param>
            /// <param name="consumerSlots">Consumer slot states (not used)</param>
            /// <param name="producerSlots">Slot states</param>
            /// <param name="elementState">State associated with the current element</param>
            /// <param name="elementNumber">Number of the current element</param>
            public void OnReleasedReceived(int consumerLinkNum, ref object elementState, int elementNumber)
            {
                // Attempt to send a new query
                m_ProducerConsumer.SendQuery(0, Slots.NO_SLOT, elementNumber);
            }
        }

        /// <summary>
        /// Instance of the event handler.
        /// </summary>
        protected EventHandler m_EventHandler;

        /// <summary>
        /// Handler class instance.
        /// </summary>
        protected IActionerHandler m_ActionerHandler;

        /// <summary>
        /// Called to initialize this task.
        /// </summary>
        public override void OnInitialize()
        {
            base.OnInitialize();

            // Check only one link per element
            if (m_ConsumerTask.NumConsumerLinks > 1)
                throw new MultipleLinksNotAllowedException("Actioner.cs: Actioners only support one link per element.");

            // Create and initialize the handler class to handle the action
            object handlerObject = GetProperty("HandlerClass");
            Type type = handlerObject as Type;
            if (type != null)
            {
                // Instantiate user-supplied handler
                m_ActionerHandler = (IActionerHandler)Activator.CreateInstance(type);
                if (m_ActionerHandler is Handler)
                {
                    Handler actionerHandler = m_ActionerHandler as Handler;
                    actionerHandler.OwningActioner = this;
                    actionerHandler.OnInitialize();
                }
            }
            else
            {
                // Instantiate default handler
                Handler actionerHandler = new Handler();
                actionerHandler.OwningActioner = this;
                m_ActionerHandler = actionerHandler;
                actionerHandler.OnInitialize();
            }

            // Get the execution priority
            int priority = (int)GetProperty("Priority");

            // Get whether this is a GUI actioner
            object oIsGui = GetProperty("IsGui");
            bool isGui = false;
            if (oIsGui is bool) isGui = (bool)oIsGui; 

            // Create the event handler
            m_EventHandler = new EventHandler(m_ConsumerTask, m_ActionerHandler, this, priority, isGui);
        }

        /// <summary>
        /// Invoked when task execution commences.
        /// </summary>
        public override void OnBeginExecution()
        {
            base.OnBeginExecution();

            // Request
            if (m_ConsumerTask.ConsumerConnectionPoint.Connections.Count > 0)
                m_ConsumerTask.SendQueryOnAllElements(0, Slots.NO_SLOT);
        }

        /// <summary>
        /// Called when an event has been received.
        /// </summary>
        /// <param name="newEvent"></param>
        public delegate void OnEvent(Event newEvent);
        public OnEvent EventReceived;
    }
}